﻿' Contains the logic to populate the diagram. Populates rows with 
' groups and nodes based on the node relationships. 
Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.Windows.Controls
Imports System.Windows
Imports System.Windows.Media
Imports System.Windows.Media.Animation
Imports System.Windows.Input
Imports System.Windows.Threading

Public Class DiagramLogic

#Region " fields "

    ' List of the connections, specify connections between two nodes.
    Private m_connections As New List(Of DiagramConnector)

    ' Map that allows quick lookup of a Item object to connection information.
    ' Used when setting up the connections between nodes.
    Private m_ItemLookup As New Dictionary(Of Item, DiagramConnectorNode)

    ' List of people, global list that is shared by all objects in the application.
    Private m_ItemWrapperItems As ItemCollection

    ' Callback when a node is clicked.
    Private m_nodeClickHandler As RoutedEventHandler

    ' Filter year for nodes and connectors.
    Private m_displayYear As Double

#End Region

#Region " properties "

    ''' <summary>
    ''' Sets the callback that is called when a node is clicked.
    ''' </summary>
    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")> _
    Public WriteOnly Property NodeClickHandler() As RoutedEventHandler
        Set(ByVal value As RoutedEventHandler)
            m_nodeClickHandler = value
        End Set
    End Property


    ''' <summary>
    ''' Gets the list of people in the family.
    ''' </summary>
    Public ReadOnly Property ItemWrapperItems() As ItemCollection
        Get
            Return m_ItemWrapperItems
        End Get
    End Property

    ''' <summary>
    ''' Gets the list of connections between nodes.
    ''' </summary>
    Public ReadOnly Property Connections() As List(Of DiagramConnector)
        Get
            Return m_connections
        End Get
    End Property

    ''' <summary>
    ''' Gets the Item lookup list. This includes all of the 
    ''' people and nodes that are displayed in the diagram.
    ''' </summary>
    Public ReadOnly Property ItemLookup() As Dictionary(Of Item, DiagramConnectorNode)
        Get
            Return m_ItemLookup
        End Get
    End Property

#End Region

    Public Sub New(ByVal ItemWrapperItems As ItemCollection)
        ' The list of people, this is a global list shared by the application.
        m_ItemWrapperItems = ItemWrapperItems

        Clear()
    End Sub



#Region " get Items "

    ''' <summary>
    ''' Return a list of parents for the people in the specified row.
    ''' </summary>
    ''' <param name="row"></param>
    ''' <returns></returns>
    Public Function GetParents(ByVal row As DiagramRow) As Collection(Of Item)
        ' List that is returned.
        Dim list As New Collection(Of Item)

        ' Get possible children in the row.
        Dim rowList As List(Of Item) = GetPrimaryAndRelatedPeople(row)

        ' Add each parent to the list, make sure the parent is only added once.
        For Each Item In rowList
            For Each parent In Item.Parents
                If Not list.Contains(parent) Then
                    list.Add(parent)
                End If
            Next
        Next
        Return list
    End Function

    ''' <summary>
    ''' Return a list of children for the people in the specified row.
    ''' </summary>
    Public Function GetChildren(ByVal row As DiagramRow) As List(Of Item)
        ' List that is returned.
        Dim list As New List(Of Item)

        ' Get possible children in the row.
        Dim rowList As List(Of Item) = GetPrimaryAndRelatedPeople(row)

        ' Add each child to the list, make sure the child is only added once.
        For Each item In rowList
            For Each child In item.Children
                If Not list.Contains(child) Then
                    list.Add(child)
                End If
            Next
        Next
        Return list
    End Function

    ''' <summary>
    ''' Return list of people in the row that are primary or related node types.
    ''' </summary>
    Private Function GetPrimaryAndRelatedPeople(ByVal row As DiagramRow) As List(Of Item)
        Dim list As New List(Of Item)
        For Each group In row.Groups
            For Each node In group.Nodes
                If (node.Type = NodeType.Related OrElse node.Type = NodeType.Primary) Then
                    list.Add(node.Item)
                End If
            Next
        Next
        Return list
    End Function

    ''' <summary>
    ''' Remove any people from the 'other' list from the 'people' list.
    ''' </summary>
    Private Sub RemoveDuplicates(ByVal items As Collection(Of Item), ByVal other As Collection(Of Item))
        For Each Item In other
            items.Remove(Item)
        Next
    End Sub

#End Region

#Region " create nodes "

    ''' <summary>
    ''' Create a DiagramNode.
    ''' </summary>
    Private Function CreateNode(ByVal item As Item, ByVal type As NodeType, ByVal clickEvent As Boolean, ByVal scale As Double) As DiagramNode
        Dim node As DiagramNode = CreateNode(item, type, clickEvent)
        node.Scale = scale
        Return node
    End Function

    ''' <summary>
    ''' Create a DiagramNode.
    ''' </summary>
    Private Function CreateNode(ByVal item As Item, ByVal type As NodeType, ByVal clickEvent As Boolean) As DiagramNode
        Dim node As New DiagramNode
        node.Item = item
        node.Type = type
        If clickEvent Then
            AddHandler node.Click, m_nodeClickHandler
        End If
        Return node
    End Function


    ''' <summary>
    ''' Add the siblings to the specified row and group.
    ''' </summary>
    Private Sub AddSiblingNodes(ByVal row As DiagramRow, ByVal group As DiagramGroup, ByVal siblings As Collection(Of Item), ByVal nodeType As NodeType, ByVal scale As Double)
        For Each sibling In siblings
            If Not ItemLookup.ContainsKey(sibling) Then
                ' Siblings node.
                Dim node As DiagramNode = CreateNode(sibling, nodeType, True, scale)
                group.Add(node)
                ItemLookup.Add(node.Item, New DiagramConnectorNode(node, group, row))
            End If
        Next
    End Sub

#End Region

#Region " create rows "

    ''' <summary>
    ''' Creates the primary row. The row contains groups: 1) The primary-group 
    ''' that only contains the primary node, and 2) The optional left-group 
    ''' that contains spouses and siblings.
    ''' </summary>
    Public Function CreatePrimaryRow(ByVal item As Item, ByVal scale As Double, ByVal scaleRelated As Double) As DiagramRow
        ' The primary node contains two groups, 
        Dim primaryGroup As New DiagramGroup
        Dim leftGroup As New DiagramGroup

        ' Set up the row.
        Dim row As New DiagramRow

        ' Add primary node.
        Dim Node As DiagramNode = CreateNode(item, NodeType.Primary, False, scale)
        primaryGroup.Add(Node)
        ItemLookup.Add(Node.Item, New DiagramConnectorNode(Node, primaryGroup, row))

        ' Siblings.
        AddSiblingNodes(row, leftGroup, item.Siblings, NodeType.Sibling, scaleRelated)

        If leftGroup.Nodes.Count > 0 Then
            leftGroup.Reverse()
            row.Add(leftGroup)
        End If
        row.Add(primaryGroup)
        Return row
    End Function

    ''' <summary>
    ''' Create the child row. The row contains a group for each child. 
    ''' Each group contains the child and spouses.
    ''' </summary>
    Public Function CreateChildrenRow(ByVal children As List(Of Item), ByVal scale As Double, ByVal scaleRelated As Double) As DiagramRow
        ' Setup the row.
        Dim row As New DiagramRow

        For Each child In children
            ' Each child is in their group, the group contains the child 
            ' and any spouses. The groups does not contain siblings.
            Dim group As New DiagramGroup
            row.Add(group)

            ' Child.
            If Not ItemLookup.ContainsKey(child) Then
                Dim node As DiagramNode = CreateNode(child, NodeType.Related, True, scale)
                group.Add(node)
                ItemLookup.Add(node.Item, New DiagramConnectorNode(node, group, row))
            End If

            ' Connections.
            AddParentConnections(child)

            group.Reverse()
        Next

        Return row
    End Function

    ''' <summary>
    ''' Create the parent row. The row contains a group for each parent. 
    ''' Each groups contains the parent, spouses and siblings.
    ''' </summary>
    Public Function CreateParentRow(ByVal parents As Collection(Of Item), ByVal scale As Double, ByVal scaleRelated As Double) As DiagramRow
        ' Set up the row.
        Dim row As New DiagramRow

        Dim groupCount As Integer = 0

        For Each item In parents
            ' Each parent is in their group, the group contains the parent,
            ' spouses and siblings.
            Dim group As New DiagramGroup
            row.Add(group)
            groupCount = groupCount + 1
            ' Determine if this is a left or right oriented group.
            Dim left As Boolean = CBool(IIf(groupCount Mod 2 = 0, True, False))
            ' Parent.
            If Not ItemLookup.ContainsKey(item) Then
                Dim node As DiagramNode = CreateNode(item, NodeType.Related, True, scale)
                group.Add(node)
                ItemLookup.Add(node.Item, New DiagramConnectorNode(node, group, row))
            End If
            ' Siblings.
            AddSiblingNodes(row, group, item.Siblings, NodeType.Sibling, scaleRelated)
            ' Connections.
            AddChildConnections(item)
            If left Then
                group.Reverse()
            End If
        Next
        ' Add connections that span across groups.
        'AddSpouseConnections(parents) 'TODO: Add supprt for Cross group connections
        Return row
    End Function

#End Region

#Region " connections "

    ''' <summary>
    ''' Add connections for each Item and the Item's children in the list.
    ''' </summary>
    Private Sub AddChildConnections(ByVal parents As Collection(Of Item))
        For Each Item In parents
            AddChildConnections(Item)
        Next
    End Sub

    ''' <summary>
    ''' Add connections between the child and child's parents.
    ''' </summary>
    Private Sub AddParentConnections(ByVal child As Item)
        For Each parent In child.Parents
            If ItemLookup.ContainsKey(parent) And ItemLookup.ContainsKey(child) Then
                Connections.Add(New ChildDiagramConnector( _
                                ItemLookup(parent), ItemLookup(child)))
            End If
        Next
    End Sub


    ''' <summary>
    ''' Add connections between the parent and parent’s children.
    ''' </summary>
    Private Sub AddChildConnections(ByVal parent As Item)
        For Each child In parent.Children
            If ItemLookup.ContainsKey(parent) And ItemLookup.ContainsKey(child) Then
                Connections.Add(New ChildDiagramConnector( _
                                       ItemLookup(parent), ItemLookup(child)))
            End If
        Next
    End Sub

#End Region

    ''' <summary>
    ''' Clear 
    ''' </summary>
    Public Sub Clear()
        ' Remove any event handlers from the nodes. Otherwise 
        ' the delegate maintains a reference to the object 
        ' which can hinder garbage collection. 
        For Each node In ItemLookup.Values
            RemoveHandler node.Node.Click, m_nodeClickHandler
        Next
        ' Clear the connection info.
        Connections.Clear()
        ItemLookup.Clear()
        'TODO: Add filter resets
    End Sub


    ''' <summary>
    ''' Return the DiagramNode for the specified Item.
    ''' </summary>
    Public Function GeDiagramNode(ByVal Item As Item) As DiagramNode
        If Item Is Nothing Then
            Return Nothing
        End If
        If Not ItemLookup.ContainsKey(Item) Then
            Return Nothing
        End If
        Return ItemLookup(Item).Node
    End Function


    ''' <summary>
    ''' Return the bounds (relative to the diagram) for the specified Item.
    ''' </summary>
    Public Function GetNodeBounds(ByVal Item As Item) As Rect
        Dim bounds As Rect = Rect.Empty
        If Not Item Is Nothing And ItemLookup.ContainsKey(Item) Then
            Dim connector As DiagramConnectorNode = ItemLookup(Item)
            bounds = New Rect(connector.TopLeft.X, connector.TopLeft.Y, _
                    connector.Node.ActualWidth, connector.Node.ActualHeight)
        End If
        Return bounds
    End Function




End Class
